package typejump.objects;

import java.awt.Graphics2D;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.Hashtable;
import java.util.LinkedList;

import typejump.engine.Animation;
import typejump.engine.GameAction;
import typejump.engine.GameEngine;
import typejump.engine.GameEvent;

/**
 * This is a GameObject that can have multiple animations, so you can
 * tell it to play "walk left" animations or whatever depending on what
 * it is.
 * @author g
 *
 */
public class Sprite extends GameObject {

	private final Object animLock = new Object();

	private volatile boolean looping = false;
	private volatile Animation running = null;
	private volatile double animTime = 0;
	private GameAction endTrigger;
	private Hashtable<String, Animation> animations = new Hashtable<String, Animation>();
	private Hashtable<Animation, GameAction> ends = new Hashtable<Animation, GameAction>();
	private LinkedList<GameAction> extras = new LinkedList<GameAction>();
	private boolean autoScale = true;

	/**
	 * If true (default), scales the size of this component to
	 * match the size of the image. If this is disabled, the image
	 * will be scaled to the component size instead.
	 * @param b
	 */
	public void setAutoScale(boolean b) {
		this.autoScale = b;
	}
	
	/**
	 * Returns the name of the currently running
	 * animation
	 * @return - the name as a String if it's registered, otherwise null
	 */
	public String getAnimationName() {
		if (running == null) return null;
		for (String name : animations.keySet()) {
			if (animations.get(name) == running)
				return name;
		}
		return null;
	}
	
	@Override
	public boolean paintEnabled() {
		return true;
	}

	/**
	 * Sets the animation that will be looped when other animations
	 * end
	 * @param string
	 */
	public void setDefaultAnimation(String string) {
		setDefaultAnimation(animations.get(string));
	}
	
	/**
	 * Sets the animation that will be looped when other animations end
	 * @param anim
	 */
	public void setDefaultAnimation(Animation anim) {
		putAnimation("default", anim);
		if (this.running == null) {
			loopAnimation("default");
		}
	}
	
	/**
	 * Returns a BufferedImage of the current frame
	 * of this Sprite (may be null if there is no
	 * animation running)
	 * @return
	 */
	public BufferedImage getCurrentFrame() {
		synchronized (animLock) {
			if (running != null) {
				return running.getImage(animTime);
			}
		}
		return null;
	}

	@Override
	public void paint(Graphics2D g) {
		if (running != null) {
			BufferedImage img = getCurrentFrame();
			g.drawImage(img, 0, 0, iw(), ih(), null);
		} else {
			g.setColor(GameEngine.getForegroundColor());
			g.drawRect(0, 0, iw()-1, ih()-1);
		}
	}
	
	public void animate(String command) {
		if (!command.matches("((play)|(loop)) .+"))
			return;
		int div = command.indexOf(' ');
		String com = command.substring(0, div);
		String anim = command.substring(div+1);
		int semi = anim.indexOf(';');
		String tail = null;
		if (semi > 0) {
			tail = anim.substring(semi+1).trim();
			anim = anim.substring(0, semi).trim();
		}
		
		if (com.equals("play"))
			playAnimation(anim);
		else if (com.equals("loop"))
			loopAnimation(anim);
		
		if (tail != null) {
			final String t = tail;
			addEndTrigger(new GameAction() {
				public void act(GameObject caller, GameObject other) {
					animate(t);
				}
			});
		}
	}

	/**
	 * Registers the given animation with the identifier String
	 * @param key
	 * @param anim
	 */
	public void putAnimation(String key, Animation anim) {
		animations.put(key, anim);
	}
	
	/**
	 * Registers the given animation with the identifier String and
	 * associated GameAction to be called when the animation completes
	 * (overrides any existing GameAction inside the Animation class)
	 * @param key
	 * @param anim
	 * @param trig
	 */
	public void putAnimation(String key, Animation anim, GameAction trig) {
		animations.put(key, anim);
		ends.put(anim, trig);
	}
	
	/**
	 * Adds a GameAction that will be called when the current animation
	 * completes (does not override existing actions)
	 * @param trig
	 */
	public void addEndTrigger(GameAction trig) {
		synchronized (animLock) {
			if (running != null) {
				extras.add(trig);
			}
		}
	}
	
	/**
	 * Runs the given animation
	 * @param anim - the animation
	 * @param endTrigger - end trigger
	 * @param looping - whether the animation should loop
	 */
	public void setAnimation(Animation anim, GameAction endTrigger, boolean looping) {
		synchronized (animLock) {
			this.running = anim;
			if (endTrigger != null) {
				this.endTrigger = endTrigger;
			} else if (ends.get(anim) == null) {
				this.endTrigger = anim.getEndTrigger();
			} else {
				this.endTrigger = ends.get(anim);
			}
			this.looping = looping;
			this.animTime = 0;
		}
	}
	
	/**
	 * Sets the action that will activate when the current
	 * animation ends.
	 * @param endTrigger
	 */
	public void setEndTrigger(GameAction endTrigger) {
		synchronized (animLock) {
			if (running != null) {
				this.endTrigger = endTrigger;
			}
		}
	}

	/**
	 * Play the specified animation
	 * @param anim
	 */
	public void playAnimation(Animation anim) {
		setAnimation(anim, null, false);
	}
	
	/**
	 * Play the specified animation and run the given
	 * GameAction afterwards (overrides any GameAction
	 * that would otherwise be run by default)
	 * @param anim
	 * @param end
	 */
	public void playAnimation(Animation anim, GameAction end) {
		setAnimation(anim, end, false);
	}
	/**
	 * Plays the animation in a loop
	 * @param anim
	 */
	public void loopAnimation(Animation anim) {
		setAnimation(anim, null, true);
	}
	
	/**
	 * Plays the animation with the given name (if found)
	 * @param anim
	 */
	public void playAnimation(String anim) {
		if (animations.get(anim) != null) {
			playAnimation(animations.get(anim));
		}
	}
	/**
	 * Plays the animation with the given name (if found), runs the given
	 * GameAction afterwards (overrides any GameAction
	 * that would otherwise be run by default)
	 * @param anim
	 * @param end
	 */
	public void playAnimation(String anim, GameAction end) {
		if (animations.get(anim) != null) {
			playAnimation(animations.get(anim), end);
		} else {
			end.act(this);
		}
	}
	/**
	 * Plays the animation with the given name in a loop (if found)
	 * @param anim
	 */
	public void loopAnimation(String anim) {
		if (animations.get(anim) != null) {
			loopAnimation(animations.get(anim));
		}
	}
	
	/**
	 * Scales the geometry of this Sprite to match
	 * the dimensions of the current frame. If there
	 * is no animation currently loaded, this method
	 * does nothing.
	 */
	public void scaleToImage() {
		synchronized (animLock) {
			if (running == null) return;
			double cx = centerX();
			double cy = centerY();
			width(running.getWidth(animTime));
			height(running.getHeight(animTime));
			centerX(cx);
			centerY(cy);
		}
	}

	/**
	 * Updates the current frame of this animation
	 * @param time
	 */
	public void updateArt(double time) {
		synchronized (animLock) {
			if (running != null) {
				animTime += time;
				if (animTime > running.getDuration() && !looping) {
					GameAction endTrigger = this.endTrigger;
					this.running = null;
					loopAnimation("default");

					for (GameAction g : extras) {
						g.act(this);
					}
					extras.clear();
					if (endTrigger != null) {
						endTrigger.act(this);
					}
				}
				if (this.running != null && autoScale) {
					scaleToImage();
				}
			}
		}
	}

	@Override
	public void update(double time) {
		updatePosition(time);
		updateArt(time);
	}

}
